/********************************************************
 *  ██████╗  ██████╗████████╗██╗
 * ██╔════╝ ██╔════╝╚══██╔══╝██║
 * ██║  ███╗██║        ██║   ██║
 * ██║   ██║██║        ██║   ██║
 * ╚██████╔╝╚██████╗   ██║   ███████╗
 *  ╚═════╝  ╚═════╝   ╚═╝   ╚══════╝
 * Geophysical Computational Tools & Library (GCTL)
 *
 * Copyright (c) 2023  Yi Zhang (yizhang-geo@zju.edu.cn)
 *
 * GCTL is distributed under a dual licensing scheme. You can redistribute 
 * it and/or modify it under the terms of the GNU Lesser General Public 
 * License as published by the Free Software Foundation, either version 2 
 * of the License, or (at your option) any later version. You should have 
 * received a copy of the GNU Lesser General Public License along with this 
 * program. If not, see <http://www.gnu.org/licenses/>.
 * 
 * If the terms and conditions of the LGPL v.2. would prevent you from using 
 * the GCTL, please consider the option to obtain a commercial license for a 
 * fee. These licenses are offered by the GCTL's original author. As a rule, 
 * licenses are provided "as-is", unlimited in time for a one time fee. Please 
 * send corresponding requests to: yizhang-geo@zju.edu.cn. Please do not forget 
 * to include some description of your company and the realm of its activities. 
 * Also add information on how to contact you by electronic and paper mail.
 ******************************************************/

#include "fir_filter.h"

static double sinc(const double x)
{
    if (x == 0)
        return 1;

    return sin(M_PI * x) / (M_PI * x);
}

gctl::fir_filter::fir_filter(){}

gctl::fir_filter::~fir_filter(){}

gctl::fir_filter::fir_filter(filting_type_e fi_type, window_type_e w_type, int taps, double fs, double f1, double f2)
{
    init(fi_type, w_type, taps, fs, f1, f2);
}

void gctl::fir_filter::init(filting_type_e fi_type, window_type_e w_type, int taps, double fs, double f1, double f2)
{
    if (taps <= 0)
    {
        throw std::invalid_argument("[GCTL] Invalid tap size for gctl::fir_filter");
    }

    if (fs <= 0 || f1 <= 0 || f2 < 0 || f1/fs >= 1)
    {
        throw std::invalid_argument("[GCTL] Invalid filtering frequency settings for gctl::fir_filter");
    }

    if (f2 > 0 && (f2/fs >= 1 || f1/fs > f2/fs))
    {
        throw std::invalid_argument("[GCTL] Invalid filtering frequency settings for gctl::fir_filter");
    }
    
    taps_ = taps;
    h_.resize(taps_, 0.0);
    w_.resize(taps_, 0.0);

    if (fi_type == LowPass) low_pass_coefficient(f1/fs);
    else if (fi_type == HighPass) high_pass_coefficient(f1/fs);
    else if (fi_type == BandPass) band_pass_coefficient(f1/fs, f2/fs);
    else if (fi_type == BandStop) band_stop_coefficient(f1/fs, f2/fs);
    else throw std::runtime_error("[GCTL] Unkown filter type for gctl::fir_filter");

    if (w_type == None) w_.assign_all(1.0);
    else if (w_type == Hamming) window_hamming(taps_, w_);
    else if (w_type == Hanning) window_hanning(taps_, w_);
    else if (w_type == Triangle) window_triangle(taps_, w_);
    else if (w_type == Blackman) window_blackman(taps_, w_);
    else throw std::runtime_error("[GCTL] Unkown window type for gctl::fir_filter");

    h_ *= w_;
    return;
}

void gctl::fir_filter::filter(const array<double> &in, array<double> &out)
{
    int id, ht = int(taps_/2);
    int is = in.size();
    out.resize(is, 0.0);

    size_t i, j;
#pragma omp parallel for private (i, j, id) schedule(guided)
    for (i = 0; i < out.size(); i++)
    {
        for (j = 0; j < taps_; j++)
        {
            id = i + j - ht;
            if (id >= 0 && id < is) out[i] += in[id]*h_[j];
        }
    }
    return;
}

gctl::array<double> &gctl::fir_filter::get_coefficients()
{
    return h_;
}

void gctl::fir_filter::low_pass_coefficient(double f)
{
    int n;
    for(int i = 0; i < taps_; i++)
    {
        n = i - int(taps_/2);
        h_[i] = 2.0*f*sinc(2.0*f*n);
    }
    return;
}

void gctl::fir_filter::high_pass_coefficient(double f)
{
    int n;
    for(int i = 0; i < taps_; i++)
    {
        n = i - int(taps_/2);
        h_[i] = sinc((double) n) - 2.0*f*sinc(2.0*f*n);
    }
    return;
}

void gctl::fir_filter::band_pass_coefficient(double f1, double f2)
{
    int n;
    for(int i = 0; i < taps_; i++)
    {
        n = i - int(taps_/2);
        h_[i] = 2.0*f1*sinc(2.0*f1*n) - 2.0*f2*sinc(2.0*f2*n);
    }
    return;
}

void gctl::fir_filter::band_stop_coefficient(double f1, double f2)
{
    int n;
    for(int i = 0; i < taps_; i++)
    {
        n = i - int(taps_/2);
        h_[i] = 2.0*f1*sinc(2.0*f1*n) - 2.0*f2*sinc(2.0*f2*n) + sinc((double) n);
    }
    return;
}